"""
定义一个函数
你可以定义一个由自己想要功能的函数，以下是简单的规则：

函数代码块以 def 关键词开头，后接函数标识符名称和圆括号 ()。
任何传入参数和自变量必须放在圆括号中间，圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数内容以冒号起始，并且缩进。
return [表达式] 结束函数，选择性地返回一个值给调用方。不带表达式的return相当于返回 None。


语法
Python 定义函数使用 def 关键字，一般格式如下：

def 函数名（参数列表）:
    函数体
默认情况下，参数值和参数名称是按函数声明中定义的顺序匹配起来的。

"""


def fibonacci(n):
    count = 0
    a = 0
    b = 1
    while count < n:
        print(b)
        m = b
        b = a+b
        a = m
        count += 1


"""
函数调用
定义一个函数：给了函数一个名称，指定了函数里包含的参数，和代码块结构。

这个函数的基本结构完成以后，你可以通过另一个函数调用执行，也可以直接从 Python 命令提示符执行。

如下实例调用了 fibonacci() 函数：
"""
fibonacci(10)


"""
参数传递
在 python 中，类型属于对象，变量是没有类型的：

a=[1,2,3]
a="Runoob"
以上代码中，[1,2,3] 是 List 类型，"Runoob" 是 String 类型
而变量 a 是没有类型，她仅仅是一个对象的引用（一个指针）
可以是指向 List 类型对象，也可以是指向 String 类型对象
"""


"""
可更改(mutable)与不可更改(immutable)对象
在 python 中，strings, tuples, 和 numbers 是不可更改的对象，而 list,dict 等则是可以修改的对象。

不可变类型：变量赋值 a=5 后再赋值 a=10，这里实际是新生成一个 int 值对象 10，再让 a 指向它，
          而 5 被丢弃，不是改变 a 的值，相当于新生成了 a。

可变类型：变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改，
        本身la没有动，只是其内部的一部分值被修改了。


python 函数的参数传递：

不可变类型：类似 C++ 的值传递，如 整数、字符串、元组。
          如 fun(a)，传递的只是 a 的值，没有影响 a 对象本身。
          如果在 fun(a)）内部修改 a 的值，则是新生成来一个 a。

可变类型：类似 C++ 的引用传递，如 列表，字典。如 fun(la)，则是将 la 真正的传过去，修改后 fun 外部的 la 也会受影响

python 中一切都是对象，严格意义我们不能说值传递还是引用传递，我们应该说传不可变对象和传可变对象。
"""

print("---------------------------------------------")
# python 传不可变对象实例
# 通过 id() 函数来查看内存地址变化：
def change(a):
    print(id(a))  # 指向的是同一个对象
    a = 10
    print(id(a))  # 一个新对象


a = 1
print(id(a))
change(a)
# 可以看见在调用函数前后，形参和实参指向的是同一个对象（对象 id 相同），
# 在函数内部修改形参后，形参指向的是不同的 id。


print("---------------------------------------------")
# 传可变对象实例
# 可变对象在函数里修改了参数，那么在调用这个函数的函数里，原始的参数也被改变了。例如：

# 可写函数说明
def changeme(mylist):
    "修改传入的列表"
    mylist.append([1, 2, 3, 4])
    print("函数内取值: ", mylist)
    return


# 调用changeme函数
mylist = [10, 20, 30]
changeme(mylist)
print("函数外取值: ", mylist)


"""
参数
以下是调用函数时可使用的正式参数类型：

必需参数
关键字参数
默认参数
不定长参数
"""

print("---------------------------------------------")
# 必需参数
# 必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
#
# 调用 printme() 函数，你必须传入一个参数，不然会出现语法错误：

# 可写函数说明
def printme(str):
    "打印任何传入的字符串"
    print(str)
    return


# 调用 printme 函数，不加参数会报错
printme("hello")


print("---------------------------------------------")
# 关键字参数
# 关键字参数和函数调用关系紧密，函数调用使用关键字参数来确定传入的参数值。
#
# 使用关键字参数允许函数调用时参数的顺序与声明时不一致，因为 Python 解释器能够用参数名匹配参数值。
#
# 以下实例在函数 printme() 调用时使用参数名：

# 可写函数说明
def printme(str):
    "打印任何传入的字符串"
    print(str)
    return


# 调用printme函数
printme(str="菜鸟教程")


# 以下实例中演示了函数参数的使用不需要使用指定顺序：
# 可写函数说明
def printinfo(name, age):
    "打印任何传入的字符串"
    print("名字: ", name)
    print("年龄: ", age)
    return


# 调用printinfo函数
printinfo(age=50, name="runoob")


print("---------------------------------------------")
# 默认参数
# 调用函数时，如果没有传递参数，则会使用默认参数。以下实例中如果没有传入 age 参数，则使用默认值：
def printinfo(name, age=35):
    "打印任何传入的字符串"
    print("名字: ", name)
    print("年龄: ", age)
    return


# 调用printinfo函数
printinfo(age=50, name="runoob")
print("----使用默认参数----")
printinfo(name="runoob")


print("---------------------------------------------")
# 不定长参数
# 你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数，和上述 2 种参数不同，声明时不会命名。基本语法如下：
#
# def functionname([formal_args,] *var_args_tuple ):
#    "函数_文档字符串"
#    function_suite
#    return [expression]

# 加了星号 * 的参数会以元组(tuple)的形式导入，存放所有未命名的变量参数。
def printinfo(arg1, *vartuple):
    "打印任何传入的参数"
    print("输出: ")
    print(arg1)
    print(vartuple)


# 调用printinfo 函数
printinfo(70, 60, 50, 40, 30)

print("------空元组------")
# 如果在函数调用时没有指定参数，它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例：
def printinfo(arg1, *vartuple):
    "打印任何传入的参数"
    print("输出: ")
    print(arg1)
    for var in vartuple:
        print(var, end=" ")
    return


# 调用printinfo 函数
printinfo(10)
printinfo(70, 60, 50, 40, 30)


# 还有一种就是参数带两个星号 **基本语法如下：
# def functionname([formal_args,] **var_args_dict ):
#    "函数_文档字符串"
#    function_suite
#    return [expression]

print("\n------ ** 字典参数 ------")
# 加了两个星号 ** 的参数会以字典的形式导入。
def printinfo(arg1, **vardict):
    "打印任何传入的参数"
    print("输出: ")
    print(arg1)
    print(vardict)


# 调用printinfo 函数
printinfo(1, a=2, b=3)


# 声明函数时，参数中星号 * 可以单独出现，例如:
#
# def f(a,b,*,c):
#     return a+b+c
# 如果单独出现星号 * 后的参数必须用关键字传入。
def f(a, b, *, c):
    return a+b+c


result = f(1, 2, c=3)
print(result)





